<!--
 Copyright (c) 2017,2018,2019,2020,2021,2022 Klaus Landsdorf (http://node-red.plus/)
 All rights reserved.
 node-red-contrib-modbus - The BSD 3-Clause License

 @author <a href="mailto:klaus.landsdorf@bianco-royal.de">Klaus Landsdorf</a> (Bianco Royal)
-->
<script type="text/javascript">
  RED.nodes.registerType('modbus-flex-server', {
    category: 'modbus',
    color: '#E9967A',
    defaults: {
      name: {value: ''},
      logEnabled: {value: false},
      serverAddress: {value: '0.0.0.0'},
      serverPort: {value: 11502, required: true, validate: RED.validators.number()},
      responseDelay: {value: 100, required: true, validate: RED.validators.number()},
      unitId: {value: 1, required: true, validate: RED.validators.number()},
      delayUnit: {
        value: 'ms', required: true, validate: function (v) {
          return 'ms' == v || 's' == v || 'm' == v || 'h' == v
        }
      },
      coilsBufferSize: {value: 20000, validate: RED.validators.number()},
      registersBufferSize: {value: 20000, validate: RED.validators.number()},
      minAddress: {value: 0, validate: RED.validators.number()},
      splitAddress: {value: 10000, validate: RED.validators.number()},
      funcGetCoil: {value: 'function getFlexCoil(addr, unitID) {\n\tif (unitID === node.unitId && \n\t\taddr >= node.minAddress && \n\t\taddr <= node.splitAddress) { \n\n\t\treturn node.coils.readUInt8(addr * node.bufferFactor) \n\t}  \n}'},
      funcGetDiscreteInput: {value: 'function getFlexDiscreteInput(addr, unitID) {\n\taddr += node.splitAddress\n\tif (unitID === node.unitId && \n\t\taddr >= node.splitAddress && \n\t\taddr <= node.splitAddress * 2) { \n\n\t\treturn node.coils.readUInt8(addr * node.bufferFactor) \n\t}  \n}'},
      funcGetInputRegister: {value: 'function getFlexInputRegister(addr, unitID) { \n\tif (unitID === node.unitId && \n\t\taddr >= node.minAddress && \n\t\taddr <= node.splitAddress) { \n\n\t\treturn node.registers.readUInt16BE(addr * node.bufferFactor)  \n\t} \n}'},
      funcGetHoldingRegister: {value: 'function getFlexHoldingRegsiter(addr, unitID) { \n\taddr += node.splitAddress\n\tif (unitID === node.unitId && \n\t\taddr >= node.splitAddress && \n\t\taddr <= node.splitAddress * 2) { \n\n\t\treturn node.registers.readUInt16BE(addr * node.bufferFactor)  \n\t} \n}'},
      funcSetCoil: {value: 'function setFlexCoil(addr, value, unitID) { \n\tif (unitID === node.unitId && \n\t\taddr >= node.minAddress && \n\t\taddr <= node.splitAddress) { \n\n\t\tnode.coils.writeUInt8(value, addr * node.bufferFactor)  \n\t} \n}'},
      funcSetRegister: {value: 'function setFlexRegister(addr, value, unitID) { \n\taddr += node.splitAddress\n\tif (unitID === node.unitId && \n\t\taddr >= node.splitAddress && \n\t\taddr <= node.splitAddress * 2) { \n\n\t\tnode.registers.writeUInt16BE(value, addr * node.bufferFactor)  \n\t} \n}'},
      showErrors: {value: false}
    },
    inputs: 1,
    outputs: 5,
    align: "right",
    icon: 'modbus.png',
    paletteLabel: 'Modbus-Flex-Server',
    label: function () {
      return this.name || 'Modbus Flex Server'
    },
    oneditprepare: function () {
      let node = this

      let tabs = RED.tabs.create({
        id: 'node-input-connector-tabs',
        onchange: function (tab) {
          $('#node-input-connector-tabs-content').children().hide()
          $('#' + tab.id).show()
        }
      })

      tabs.addTab({
        id: 'modbus-server-tab-settings',
        label: this._('modbus-contrib.tabs-label.settings')
      })

      tabs.addTab({
        id: 'modbus-server-tab-getCoil',
        label: this._('modbus-contrib.tabs-label.getCoil')
      })

      tabs.addTab({
        id: 'modbus-server-tab-getDiscreteInput',
        label: this._('modbus-contrib.tabs-label.getDiscreteInput')
      })

      tabs.addTab({
        id: 'modbus-server-tab-getInput',
        label: this._('modbus-contrib.tabs-label.getInput')
      })

      tabs.addTab({
        id: 'modbus-server-tab-getHolding',
        label: this._('modbus-contrib.tabs-label.getHolding')
      })

      tabs.addTab({
        id: 'modbus-server-tab-setCoil',
        label: this._('modbus-contrib.tabs-label.setCoil')
      })

      tabs.addTab({
        id: 'modbus-server-tab-setRegister',
        label: this._('modbus-contrib.tabs-label.setRegister')
      })

      let editorGlobals = {
        msg: true,
        context: true,
        RED: true,
        util: true,
        flow: true,
        global: true,
        console: true,
        Buffer: true,
        setTimeout: true,
        clearTimeout: true,
        setInterval: true,
        clearInterval: true
      }

      node.editorGetCoil = RED.editor.createEditor({
        id: 'node-input-func-editor-getCoil',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcGetCoil').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-getCoil" ).resizable();
      } );

      let getCoilElement = document.getElementById('node-input-func-editor-getCoil');
      getCoilElement.addEventListener("mouseup", resizeScriptGetCoil, false);
      function resizeScriptGetCoil () {
        node.editorGetCoil.resize()
      }

      node.editorGetDiscreteInput = RED.editor.createEditor({
        id: 'node-input-func-editor-getDiscreteInput',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcGetDiscreteInput').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-getDiscreteInput" ).resizable();
      } );

      let getDiscreteInputElement = document.getElementById('node-input-func-editor-getDiscreteInput');
      getDiscreteInputElement.addEventListener("mouseup", resizeScriptGetDiscreteInput, false);
      function resizeScriptGetDiscreteInput () {
        node.editorGetDiscreteInput.resize()
      }

      node.editorGetInput = RED.editor.createEditor({
        id: 'node-input-func-editor-getInputRegister',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcGetInputRegister').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-getInputRegister" ).resizable();
      } );

      let getInputRegisterElement = document.getElementById('node-input-func-editor-getInputRegister');
      getInputRegisterElement.addEventListener("mouseup", resizeScriptGetInputRegister, false);
      function resizeScriptGetInputRegister () {
        node.editorGetInput.resize()
      }

      node.editorGetHolding = RED.editor.createEditor({
        id: 'node-input-func-editor-getHoldingRegister',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcGetHoldingRegister').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-getHoldingRegister" ).resizable();
      } );

      let getHoldingRegisterElement = document.getElementById('node-input-func-editor-getHoldingRegister');
      getHoldingRegisterElement.addEventListener("mouseup", resizeScriptEditorGetHolding, false);
      function resizeScriptEditorGetHolding () {
        node.editorGetHolding.resize()
      }

      node.editorSetCoil = RED.editor.createEditor({
        id: 'node-input-func-editor-setCoil',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcSetCoil').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-setCoil" ).resizable();
      } );

      let setCoilElement = document.getElementById('node-input-func-editor-setCoil');
      setCoilElement.addEventListener("mouseup", resizeScriptSetCoil, false);
      function resizeScriptSetCoil () {
        node.editorSetCoil.resize()
      }

      node.editorSetRegister = RED.editor.createEditor({
        id: 'node-input-func-editor-setRegister',
        mode: 'ace/mode/javascript',
        value: $('#node-input-funcSetRegister').val(),
        globals: editorGlobals
      })

      $( function() {
        $( "#node-input-func-editor-setRegister" ).resizable();
      } );

      let setRegisterElement = document.getElementById('node-input-func-editor-setRegister');
      setRegisterElement.addEventListener("mouseup", resizeScriptSetRegister, false);
      function resizeScriptSetRegister () {
        node.editorSetRegister.resize()
      }
    },
    oneditsave: function () {
      $('#node-input-funcGetCoil').val(this.editorGetCoil.getValue())
      this.editorGetCoil.destroy()
      delete this.editorGetCoil

      $('#node-input-funcGetDiscreteInput').val(this.editorGetDiscreteInput.getValue())
      this.editorGetDiscreteInput.destroy()
      delete this.editorGetDiscreteInput

      $('#node-input-funcGetInputRegister').val(this.editorGetInput.getValue())
      this.editorGetInput.destroy()
      delete this.editorGetInput

      $('#node-input-funcGetHoldingRegister').val(this.editorGetHolding.getValue())
      this.editorGetHolding.destroy()
      delete this.editorGetHolding

      $('#node-input-funcSetCoil').val(this.editorSetCoil.getValue())
      this.editorSetCoil.destroy()
      delete this.editorSetCoil

      $('#node-input-funcSetRegister').val(this.editorSetRegister.getValue())
      this.editorSetRegister.destroy()
      delete this.editorSetRegister
    },
    oneditcancel: function () {
      this.editorGetCoil.destroy()
      delete this.editorGetCoil

      this.editorGetDiscreteInput.destroy()
      delete this.editorGetDiscreteInput

      this.editorGetInput.destroy()
      delete this.editorGetInput

      this.editorGetHolding.destroy()
      delete this.editorGetHolding

      this.editorSetCoil.destroy()
      delete this.editorSetCoil

      this.editorSetRegister.destroy()
      delete this.editorSetRegister
    },
    oneditresize: function (size) {
      let rows = $('#dialog-form>div:not(.node-text-editor-row)')
      let height = $('#dialog-form').height()

      for (let i = 0; i < rows.size(); i++) {
        height -= $(rows[i]).outerHeight(true)
      }

      let editorRow = $('#dialog-form>div.node-text-editor-row')
      height -= (parseInt(editorRow.css('marginTop')) + parseInt(editorRow.css('marginBottom')))

      $('.node-text-editor').css('height', height + 'px')
      this.editorGetCoil.resize()
      this.editorGetDiscreteInput.resize()
      this.editorGetInput.resize()
      this.editorGetHolding.resize()
      this.editorSetCoil.resize()
      this.editorSetRegister.resize()

    }
  })
</script>


<script type="text/x-red" data-template-name="modbus-flex-server">
    <div class="form-row">
        <ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-input-connector-tabs"></ul>
    </div>
    <div id="node-input-connector-tabs-content" style="min-height: 170px;">
        <div id="modbus-server-tab-settings" style="display:none">
            <div class="form-row">
                <label for="node-input-name"><i class="icon-tag"></i> Name</label>
                <input type="text" id="node-input-name" placeholder="Name">
            </div>
            <div class="form-row">
                <label for="node-input-serverAddress"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.address"></span></label>
                <input type="text" id="node-input-serverAddress" placeholder="0.0.0.0 (IPv4) or :: (IPv6)">
            </div>
            <div class="form-row">
                <label for="node-input-serverPort"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.port"></span></label>
                <input type="text" id="node-input-serverPort" placeholder="2000 to 65536">
            </div>
            <div class="form-row">
                <label for="node-input-responseDelay"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.responseDelay"></span></label>
                <input type="text" id="node-input-responseDelay" placeholder="100" style="max-width:120px">
                <select id="node-input-delayUnit" style="max-width:160px">
                    <option value="ms">millisecond(s)</option>
                    <option value="s">second(s)</option>
                    <option value="m">minute(s)</option>
                    <option value="h">hour(s)</option>
                </select>
            </div>
            <div class="form-row">
                <label for="node-input-unitId"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.unitId"></span></label>
                <input type="text" id="node-input-unitId" placeholder="1">
            </div>
            <div class="form-row">
                <label for="node-input-coilsBufferSize"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.coils"></span></label>
                <input type="text" id="node-input-coilsBufferSize" placeholder="20000">
            </div>
            <div class="form-row">
                <label for="node-input-registersBufferSize"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.registers"></span></label>
                <input type="text" id="node-input-registersBufferSize" placeholder="20000">
            </div>
            <hr>
            <div class="form-row">
                <label for="node-input-minAddress"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.minAddress"></span></label>
                <input type="text" id="node-input-minAddress" placeholder="0">
            </div>
            <div class="form-row">
                <label for="node-input-splitAddress"><i class="icon-tag"></i> <span data-i18n="modbus-contrib.label.splitAddress"></span></label>
                <input type="text" id="node-input-splitAddress" placeholder="10000">
            </div>
            <hr>
            <div class="form-row">
                <label style="min-width:190px" for="node-input-showErrors"><i class="fa fa-th"></i> <span
                data-i18n="modbus-contrib.label.showErrors"></span></label>
                <input type="checkbox" id="node-input-showErrors" style="max-width:30px">
            </div>
            <div class="form-row">
                <label style="min-width:190px" for="node-input-logEnabled"><i class="fa fa-th"></i> <span data-i18n="modbus-contrib.label.logEnabled"></span></label>
                <input type="checkbox" id="node-input-logEnabled" style="max-width:30px">
            </div>
        </div>
        <div id="modbus-server-tab-getCoil" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcGetCoil"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcGetCoil">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-getCoil"></div>
            </div>
        </div>
        <div id="modbus-server-tab-getDiscreteInput" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcGetDiscreteInput"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcGetDiscreteInput">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-getDiscreteInput"></div>
            </div>
        </div>
        <div id="modbus-server-tab-getInput" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcGetInputRegister"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcGetInputRegister">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-getInputRegister"></div>
            </div>
        </div>
        <div id="modbus-server-tab-getHolding" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcGetHoldingRegister"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcGetHoldingRegister">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-getHoldingRegister"></div>
            </div>
        </div>
        <div id="modbus-server-tab-setCoil" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcSetCoil"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcSetCoil">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-setCoil"></div>
            </div>
        </div>
        <div id="modbus-server-tab-setRegister" style="display:none">
            <div class="form-row" style="margin-bottom: 0px;">
                <label for="node-input-funcSetRegister"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
                <input type="hidden" id="node-input-funcSetRegister">
            </div>
            <div class="form-row node-text-editor-row">
                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-setRegister"></div>
            </div>
        </div>
    </div>

</script>


<script type="text/x-red" data-help-name="modbus-flex-server">
    <p>Node to provide a flexible Modbus TCP server based on modbus-serial for testing.</p>

    <p>
        The modbus-serial package allows to configure/inject code to handle modbus requests.
        With that you can build you personal Modbus server.
        The function frame is fix - just the body of functions is flexible.
    </p>

    <p>On injecting the server sends the Buffers to the separate outputs</p>
    <p>You can use the Modbus write nodes (FC) to write data to the server buffers.</p>
    <p>You can use the Modbus read nodes (FC) to read data from the server buffers.</p>

    <p>Output 1: holding Buffer, type, msg</p>
    <p>Output 2: coils Buffer, type, msg</p>
    <p>Output 3: input Buffer, type, msg</p>
    <p>Output 4: discrete inputs Buffer, type, msg</p>
    <br>
    <p>Input:
        On injecting a special payload, you can write directly to any register.
        This should only be used if you want to simulate a Modbus client.
        <code>
            msg.payload = {
                'value': msg.payload,
                'register': 'holding',
                'address': 1 ,
                'disableMsgOutput' : 0
            };
            return msg;
        </code>
    </p>
    <p>The value could also be a list of UInt8 numbers and they will be written to the buffer.</p>
    <p>
        Valid registers are:
        <ul>
            <li>holding
            <li>coils
            <li>input
            <li>discrete
        </ul>
        Set disableMsgOutput if you want to disable the Flex Server outputs when injecting.
    </p>
</script>
